export class Coordinate {
    static readonly EARTH_RADIUS_LONG = 63.78137;
    static readonly EARTH_RADIUS_SHORT = 63.567523142;
    public static readonly NESWRegStr = '[NSEW]\\d+.\\d+.\\d+.\\d+';
    private static NESW2223Regex: RegExp = new RegExp('(?<dir>[NSEW])(?<deg>\\d+).(?<min>\\d+).(?<sec>\\d+.\\d+)');
    private static latitudeStrRegex: RegExp = new RegExp('[SN]');
    private static longitudeStrRegex: RegExp = new RegExp('[WE]');
    private static degRegex: RegExp = new RegExp('(?<dir>[NSEW])(?<deg>\\d+.\\d+)');
    private static decimalRegex: RegExp = new RegExp('\\d+.\\d+');
    /**
     * 纬度
     */
    latitude: number = 0;
    /**
     * 经度
     */
    longitude: number = 0;

    static deg2rad(deg: number): number {
        return Math.PI * (deg / 180);
    }

    static isLatitudeStr(str: string): boolean {
        return Coordinate.latitudeStrRegex.test(str);
    }

    static isLongitudeStr(str: string): boolean {
        return Coordinate.longitudeStrRegex.test(str);
    }

    static isCoordinateStr(str: string): boolean {
        return Coordinate.NESW2223Regex.test(str);
    }

    static fromNumber(latitude: number, longitude: number): Coordinate {
        const result = new Coordinate();
        result.latitude = latitude;
        result.longitude = longitude;
        return result;
    }

    static fromStr(latitude: string, longitude: string): Coordinate {
        const result = new Coordinate();
        if (Coordinate.NESW2223Regex.test(latitude)) {
            result.latitude = Coordinate._fromNESW2223Str(latitude);
        } else if (Coordinate.decimalRegex.test(latitude)) {
            result.latitude = Coordinate._fromDecimalStr(latitude, 'latitude');
        } else if (Coordinate.degRegex.test(latitude)) {
            result.latitude = Coordinate._fromDegStr(latitude);
        }
        if (Coordinate.NESW2223Regex.test(longitude)) {
            result.longitude = Coordinate._fromNESW2223Str(longitude);
        } else if (Coordinate.decimalRegex.test(longitude)) {
            result.longitude = Coordinate._fromDecimalStr(longitude, 'longitude');
        } else if (Coordinate.degRegex.test(longitude)) {
            result.longitude = Coordinate._fromDegStr(longitude);
        }
        return result;
    }

    private static correctLat(lat: number) {
        const rad = Coordinate.deg2rad(lat);
        return Coordinate.EARTH_RADIUS_LONG * Math.log((1.0 + Math.sin(rad)) / (1.0 - Math.sin(rad))) / 2;
    }

    private static correctLon(lon: number) {
        return Coordinate.deg2rad(lon) * Coordinate.EARTH_RADIUS_LONG;
    }

    private static _fromDecimalStr(str: string, type: 'latitude' | 'longitude'): number {
        if (!Coordinate.decimalRegex.test(str)) return 0;
        let val = Number(str);
        if (type == 'latitude') {
            return Coordinate.correctLat(val);
        } else {
            return Coordinate.correctLon(val);
        }
    }

    private static _fromDegStr(str: string): number {
        const group = Coordinate.degRegex.exec(str)?.groups;
        if (group == undefined) return 0;
        let result = Number(group.deg);
        const dir = group.dir;
        if (result <= 0) return 0;
        if (dir == 'E' || dir == 'W') {
            if (dir == 'W') {
                result = -result;
            }
            if (result > 180) {
                return Coordinate.correctLon(180);
            }
            return Coordinate.correctLon(result);
        }
        if (dir == 'S' || dir == 'N') {
            if (dir == 'N') {
                result = -result;
            }
            if (result > 90) {
                return Coordinate.correctLat(90);
            }
            return Coordinate.correctLat(result);
        }
        return 0;
    }

    private static _fromNESW2223Str(str: string): number {
        let result: number = 0;
        const group = Coordinate.NESW2223Regex.exec(str)?.groups;
        if (group == undefined) {
            return result;
        }
        const deg = Number(group.deg);
        const min = Number(group.min);
        const sec = Number(group.sec);
        const dir = group.dir;
        result = deg + min / 60 + sec / 3600;
        if (result <= 0) return 0;
        if (dir == 'E' || dir == 'W') {
            if (dir == 'W') {
                result = -result;
            }
            if (result > 180) {
                return Coordinate.correctLon(180);
            }
            return Coordinate.correctLon(result);
        }
        if (dir == 'S' || dir == 'N') {
            if (dir == 'N') {
                result = -result;
            }
            if (result > 90) {
                return Coordinate.correctLat(90);
            }
            return Coordinate.correctLat(result);
        }
        return 0;
    }

    setFromStr(str: string) {
        if (Coordinate.NESW2223Regex.test(str)) {
            if (Coordinate.isLatitudeStr(str)) {
                this.latitude = Coordinate._fromNESW2223Str(str);
            } else if (Coordinate.isLongitudeStr(str)) {
                this.longitude = Coordinate._fromNESW2223Str(str);
            }
        } else if (Coordinate.degRegex.test(str)) {
            if (Coordinate.isLatitudeStr(str)) {
                this.latitude = Coordinate._fromDegStr(str);
            } else if (Coordinate.isLongitudeStr(str)) {
                this.longitude = Coordinate._fromDegStr(str);
            }
        }
        return this;
    }

    equal(other: Coordinate): boolean {
        return this.latitude === other.latitude && this.longitude === other.longitude;
    }
}